/* Copyright (c) 2009, Michael Patraw
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * The name of Michael Patraw may not be used to endorse or promote
 *       products derived from this software without specific prior written
 *       permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Michael Patraw ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Michael Patraw BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include "board.h"

#include <stdlib.h>
#include <string.h>

#include "at.h"
#include "display.h"
#include "load.h"

/* Draws a tile to the screen.
 */
static void _Board_DrawTile(AtWindow * win, int x, int y, int tile);

/* Recursively pushes tiles.
 */
static void _Board_PushTile(board_t * board, int x, int y, int dir, int tile);

board_t * Board_Create(int width, int height)
{
    int x, y, arr_size;
    
    if (width > BOARD_MAX_WIDTH)
        width = BOARD_MAX_WIDTH;
    if (height > BOARD_MAX_HEIGHT)
        height = BOARD_MAX_HEIGHT;

    arr_size = width * height * sizeof(int);
    
    board_t * ret = (board_t *)malloc(sizeof(board_t) + arr_size);
    
    if (ret) {
        ret->width = width;
        ret->height = height;
        
        for (y = 0; y < height; ++y)
            for (x = 0; x < width; ++x)
                BOARD_SQUARE(ret, x, y) = HAS_NOTHING;
        
        /* Set borders. */
        x = 0;
        for (y = 0; y < height; ++y)
            BOARD_SQUARE(ret, x, y) = HAS_WALL;
        y = 0;
        for (x = 0; x < width; ++x)
            BOARD_SQUARE(ret, x, y) = HAS_WALL;

        x = width - 1;
        for (y = 0; y < height; ++y)
            BOARD_SQUARE(ret, x, y) = HAS_WALL;
        y = height - 1;
        for (x = 0; x < width; ++x)
            BOARD_SQUARE(ret, x, y) = HAS_WALL;
    }
    
    return ret;
}

void Board_Destroy(board_t * board)
{
    if (board)
        free(board);
}

void Board_Draw(board_t * board, unsigned int win_id)
{
    int x, y;
    AtWindow * win = Display_RetrieveWindow(win_id);
    
    for (y = 0; y < board->height; ++y)
        for (x = 0; x < board->width; ++x)
            _Board_DrawTile(win, x, y, BOARD_SQUARE(board, x, y));
}

bool Board_BadLocation(board_t * board, int x, int y)
{
    if (x < 0 || x > board->width || y < 0 || y > board->height)
        return true;

    if (BOARD_SQUARE(board, x, y) != HAS_NOTHING)
        return true;

    return false;
}

int Board_ValidDirections(board_t * board, int x, int y)
{
    int tx, ty; /* Temp x and y. */
    int ret = 0;

    if (board == NULL)
        return -1;

    /* Up */
    for (tx = x, ty = y - 1; ty >= 0; --ty) {
        if (BOARD_SQUARE(board, tx, ty) == HAS_NOTHING) {
            ret |= UP;
            break;
        }
        else if (BOARD_SQUARE(board, tx, ty) == HAS_IMM_OBJECT) {
            break;
        }
        else if (BOARD_SQUARE(board, tx, ty) == HAS_WALL) {
            break;
        }
    }

    /* Down */
    for (tx = x, ty = y + 1; ty < board->height; ++ty) {
        if (BOARD_SQUARE(board, tx, ty) == HAS_NOTHING) {
            ret |= DOWN;
            break;
        }
        else if (BOARD_SQUARE(board, tx, ty) == HAS_IMM_OBJECT) {
            break;
        }
        else if (BOARD_SQUARE(board, tx, ty) == HAS_WALL) {
            break;
        }
    }

    /* Left */
    for (tx = x - 1, ty = y; tx >= 0; --tx) {
        if (BOARD_SQUARE(board, tx, ty) == HAS_NOTHING) {
            ret |= LEFT;
            break;
        }
        else if (BOARD_SQUARE(board, tx, ty) == HAS_IMM_OBJECT) {
            break;
        }
        else if (BOARD_SQUARE(board, tx, ty) == HAS_WALL) {
            break;
        }
    }

    /* Right */
    for (tx = x + 1, ty = y; tx < board->width; ++tx) {
        if (BOARD_SQUARE(board, tx, ty) == HAS_NOTHING) {
            ret |= RIGHT;
            break;
        }
        else if (BOARD_SQUARE(board, tx, ty) == HAS_IMM_OBJECT) {
            break;
        }
        else if (BOARD_SQUARE(board, tx, ty) == HAS_WALL) {
            break;
        }
    }

    return ret;
}

void Board_PushDirection(board_t * board, int x, int y, int dir)
{
    int prev_tile;
    
    if (board == NULL)
        return;
    else if (!(Board_ValidDirections(board, x, y) & dir))
        return;

    /* Step until a blank square is found. We checked if it was a
       valid direction so walls and immovable objects aren't a problem. */     
    prev_tile = BOARD_SQUARE(board, x, y);
    Board_StepDirection(&x, &y, dir);
    _Board_PushTile(board, x, y, dir, prev_tile);
    BOARD_SQUARE(board, x, y) = HAS_NOTHING;
}

void Board_StepDirection(int * x, int * y, int dir)
{
    if ((dir & UP) && y)
        --(*y);
    if ((dir & DOWN) && y)
        ++(*y);
    if ((dir & LEFT) && x)
        --(*x);
    if ((dir & RIGHT) && x)
        ++(*x);
}

int Board_GetDirection(int x, int y)
{
    int ret = 0;
    
    if (y == -1)
        ret |= UP;
    if (y == 1)
        ret |= DOWN;
    if (x == -1)
        ret |= LEFT;
    if (x == 1)
        ret |= RIGHT;
    
    if (ret == 0)
        ret = -1;
    
    return ret;
}

static void _Board_DrawTile(AtWindow * win, int x, int y, int tile)
{
    switch (tile) {
        case HAS_NOTHING:
            atWindowDrawChar(win, x, y, FLOOR_COLOR,
                    atWindowGetBgColor(win), FLOOR_TILE);
            break;
        case HAS_OBJECT:
            atWindowDrawChar(win, x, y, OBJECT_COLOR,
                    atWindowGetBgColor(win), OBJECT_TILE);
            break;
        case HAS_IMM_OBJECT:
            atWindowDrawChar(win, x, y, IMM_OBJECT_COLOR,
                    atWindowGetBgColor(win), IMM_OBJECT_TILE);
            break;
        case HAS_WALL:
            atWindowDrawChar(win, x, y, WALL_COLOR,
                    atWindowGetBgColor(win), WALL_TILE);
            break;
        default:
            break;
    }
}

static void _Board_PushTile(board_t * board, int x, int y, int dir, int tile)
{
    if (board == NULL) {
        return;
    }
    else if (BOARD_SQUARE(board, x, y) == HAS_NOTHING) {
        BOARD_SQUARE(board, x, y) = tile;
    }
    else {
        tile = BOARD_SQUARE(board, x, y);
        Board_StepDirection(&x, &y, dir);
        _Board_PushTile(board, x, y, dir, tile);
    }
}
